跳到主要内容

JSON 反序列化

JSON 字符串转对象(反序列化)主要用 ObjectMapper#readValue,按目标类型有三种重载:

public <T> T readValue(String content, Class<T> valueType);
public <T> T readValue(String content, JavaType valueType);
public <T> T readValue(String content, TypeReference<T> valueTypeRef);

入参源除 String 外,还有 byte[]FileURLInputStream 一组同样签名的重载。

字符串转对象

String json = "{\"name\":\"张三\",\"age\":18}";
User user = objectMapper.readValue(json, User.class);

简单类型用 Class 重载即可。带泛型的对象(通用的 Result<T> 响应封装)直接传 Result.class 会丢类型,见下节。

带泛型的对象(Result<T>)

服务调用通常会包一层通用响应:

@Getter
@Setter
public class Result<T> {
private int code;
private String message;
private T data;
}

对应的 JSON:

{"code":200,"message":"ok","data":{"name":"张三","age":18}}

直接 readValue(json, Result.class) 不行,data 会被解析成 LinkedHashMap,拿不到真正的 User。根因是泛型擦除:运行期 Result<User>Result<Order> 是同一个 Result.class,Jackson 无从得知 data 的具体类型,只能退化成 Map

TypeReference,类型在编码时已知的业务代码里最直观:

Result<User> result = objectMapper.readValue(json, new TypeReference<Result<User>>() {});
User user = result.getData(); // 真正的 User 对象

TypeFactory,适合类型要动态拼装(元素类型是方法参数、做通用工具)的场景:

JavaType type = objectMapper.getTypeFactory()
.constructParametricType(Result.class, User.class);
Result<User> result = objectMapper.readValue(json, type);

constructParametricType 封进工具类后,调用就是一行:

Result<User> result = toObj(json, Result.class, User.class);
备注

「字符串转集合」里说 TypeReference 不推荐,是针对封装通用工具方法的语境(元素类型是参数,TypeReference 写死泛型没法参数化)。业务代码里类型固定时,TypeReference 反而最清晰。

无返回值(Result<Void>)

VoidT 的特例,表示「有响应壳、无返回体」,常用于 PUT / DELETE 这类无返回值的接口。两种写法照常可用,data 解析为 null:

Result<Void> result = objectMapper.readValue(json, new TypeReference<Result<Void>>() {});
// 或
JavaType type = objectMapper.getTypeFactory().constructParametricType(Result.class, Void.class);
Result<Void> result = objectMapper.readValue(json, type);

如果压根不读 data,直接用 raw type 省去泛型声明也行:

Result result = objectMapper.readValue(json, Result.class);

区别在类型安全:Result<Void> 保证 data 是 null;raw type 下 data 可能是 LinkedHashMap(JSON 里带了值时),容易被误用。要明确表达「无返回体」,用 Result<Void>

注意

Void 不可实例化,即便 JSON 的 data 带了非 null 值,Jackson 也会把它置成 null 而不报错,能容忍脏数据,但不会替你校验「data 本应为空」。

多层泛型嵌套

Result<List<User>> 这种嵌套,TypeReference 一步到位:

Result<List<User>> result = objectMapper.readValue(json, new TypeReference<Result<List<User>>>() {});

TypeFactory 写法要逐层构造,多层时更啰嗦,优先用 TypeReference

JavaType userList = objectMapper.getTypeFactory().constructCollectionType(List.class, User.class);
JavaType type = objectMapper.getTypeFactory().constructParametricType(Result.class, userList);
Result<List<User>> result = objectMapper.readValue(json, type);

字符串转集合

集合泛型擦除,readValue(json, List<User>.class) 行不通,要用 TypeFactory 构造集合类型:

CollectionType type = objectMapper.getTypeFactory()
.constructCollectionType(List.class, User.class);
List<User> users = objectMapper.readValue(json, type);

TypeReference 也能写,但匿名内部类会留下一个 .class 文件,一般不推荐:

List<User> users = objectMapper.readValue(json, new TypeReference<List<User>>() {});

封装成泛型方法,把类型构造藏到工具类里:

public static <C extends Collection<E>, E> C toCollection(
String json, Class<C> collection, Class<E> element) {
CollectionType type = MAPPER.getTypeFactory().constructCollectionType(collection, element);
return MAPPER.readValue(json, type);
}

// List<User> users = toCollection(json, ArrayList.class, User.class);

字符串转 Map

同理用 constructMapType

MapType type = objectMapper.getTypeFactory()
.constructMapType(Map.class, String.class, Integer.class);
Map<String, Integer> map = objectMapper.readValue(json, type);

从文件 / URL / 流读取

签名一致,把 String 换成对应入参即可。文件不要求扩展名是 .json,只要内容是合法 JSON 就能解析:

User fromFile = objectMapper.readValue(new File("/path/data.txt"), User.class);
User fromUrl = objectMapper.readValue(new URL("file:/path/data.txt"), User.class);
User fromIn = objectMapper.readValue(new FileInputStream(file), User.class);

忽略未知字段

JSON 多了目标类没有的字段时,默认会抛 UnrecognizedPropertyException。对接外部接口、字段经常变动时,关掉这个校验:

objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

Java8 日期问题

LocalDateTime 等字段的 JSON 默认反序列化会失败,需要注册 JavaTimeModule,见 java.time 日期格式问题